package net.aiion.weave.impl.bytecode;

import net.aiion.weave.asm.MethodVisitor;
import net.aiion.weave.spi.bytecode.BytecodeAnnotationVisitor;
import net.aiion.weave.spi.bytecode.BytecodeClassVisitor;
import net.aiion.weave.spi.bytecode.BytecodeFieldVisitor;
import net.aiion.weave.spi.bytecode.BytecodeMethodVisitor;
import net.aiion.weave.spi.bytecode.events.AnnotationDefaultStartBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.AnnotationStartBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.AttributeBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.CodeEndBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.CodeStartBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.FieldInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.FrameBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.IincInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.InstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.IntInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.InvokeDynamicInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.JumpInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.LabelBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.LineNumberBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.LoadConstantInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.LocalVariableBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.LookupSwitchInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.MethodEndBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.MethodInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.MultiANewArrayInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.ParameterAnnotationStartBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.TableSwitchInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.TryCatchBlockBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.TypeInstructionBytecodeEvent;
import net.aiion.weave.spi.bytecode.events.VariableInstructionBytecodeEvent;

public class BytecodeMethodWriterVisitor implements BytecodeMethodVisitor
{
    private final MethodVisitor mMethodVisitor;

    public BytecodeMethodWriterVisitor(final MethodVisitor aMethodVisitor) {
        mMethodVisitor = aMethodVisitor;
    }

    @Override
    public BytecodeClassVisitor asBytecodeClassVisitor() {
        return null;
    }

    @Override
    public BytecodeAnnotationVisitor asBytecodeAnnotationVisitor() {
        return null;
    }

    @Override
    public BytecodeFieldVisitor asBytecodeFieldVisitor() {
        return null;
    }

    @Override
    public BytecodeMethodVisitor asBytecodeMethodVisitor() {
        return this;
    }

    @Override
    public BytecodeAnnotationVisitor visitAnnotationDefaultStart(
        final AnnotationDefaultStartBytecodeEvent aAnnotationDefaultStartBytecodeEvent)
    {
        return new BytecodeAnnotationWriterVisitor(mMethodVisitor.visitAnnotationDefault());
    }

    @Override
    public BytecodeAnnotationVisitor visitAnnotationStart(
        final AnnotationStartBytecodeEvent aAnnotationStartBytecodeEvent)
    {
        return new BytecodeAnnotationWriterVisitor(mMethodVisitor.visitAnnotation(
            aAnnotationStartBytecodeEvent.getDescriptor(), aAnnotationStartBytecodeEvent.getVisible()));
    }

    @Override
    public BytecodeAnnotationVisitor visitParameterAnnotationStart(
        final ParameterAnnotationStartBytecodeEvent aParameterAnnotationStartBytecodeEvent)
    {
        return new BytecodeAnnotationWriterVisitor(
            mMethodVisitor.visitParameterAnnotation(aParameterAnnotationStartBytecodeEvent.getParameterIndex(),
                aParameterAnnotationStartBytecodeEvent.getDescriptor(),
                aParameterAnnotationStartBytecodeEvent.getVisible()));
    }

    @Override
    public void visitAttribute(final AttributeBytecodeEvent aAttributeBytecodeEvent) {
        mMethodVisitor.visitAttribute(aAttributeBytecodeEvent.getAttribute());
    }

    @Override
    public void visitCodeStart(final CodeStartBytecodeEvent aCodeStartBytecodeEvent) {
        mMethodVisitor.visitCode();
    }

    @Override
    public void visitFrame(final FrameBytecodeEvent aFrameBytecodeEvent) {
        mMethodVisitor.visitFrame(aFrameBytecodeEvent.getFrameType(), aFrameBytecodeEvent.getLocalVariables(),
            aFrameBytecodeEvent.getLocalVariableTypes(), aFrameBytecodeEvent.getStackElements(),
            aFrameBytecodeEvent.getStackElementTypes());
    }

    @Override
    public void visitFieldInstruction(final FieldInstructionBytecodeEvent aFieldInstructionBytecodeEvent) {
        mMethodVisitor.visitFieldInsn(aFieldInstructionBytecodeEvent.getOpcode(),
            aFieldInstructionBytecodeEvent.getOwnerName(), aFieldInstructionBytecodeEvent.getFieldName(),
            aFieldInstructionBytecodeEvent.getFieldDescriptor());
    }

    @Override
    public void visitIincInstruction(final IincInstructionBytecodeEvent aIincInstructionBytecodeEvent) {
        mMethodVisitor.visitIincInsn(aIincInstructionBytecodeEvent.getLocalVariableIndex(),
            aIincInstructionBytecodeEvent.getIncrement());
    }

    @Override
    public void visitInstruction(final InstructionBytecodeEvent aInstructionBytecodeEvent) {
        mMethodVisitor.visitInsn(aInstructionBytecodeEvent.getOpcode());
    }

    @Override
    public void visitIntInstruction(final IntInstructionBytecodeEvent aIntInstructionBytecodeEvent) {
        mMethodVisitor
            .visitIntInsn(aIntInstructionBytecodeEvent.getOpcode(), aIntInstructionBytecodeEvent.getOperand());
    }

    @Override
    public void visitInvokeDynamicInstruction(final InvokeDynamicInstructionBytecodeEvent aInstructionBytecodeEvent) {
        mMethodVisitor.visitInvokeDynamicInsn(aInstructionBytecodeEvent.getMethodName(),
            aInstructionBytecodeEvent.getMethodDescriptor(), aInstructionBytecodeEvent.getBootstrapMethodHandle(),
            aInstructionBytecodeEvent.getBootstrapMethodArguments());
    }

    @Override
    public void visitJumpInstruction(final JumpInstructionBytecodeEvent aJumpInstructionBytecodeEvent) {
        mMethodVisitor.visitJumpInsn(aJumpInstructionBytecodeEvent.getOpcode(),
            aJumpInstructionBytecodeEvent.getLabel());
    }

    @Override
    public void visitLoadConstantInstruction(
        final LoadConstantInstructionBytecodeEvent aLoadConstantInstructionBytecodeEvent)
    {
        mMethodVisitor.visitLdcInsn(aLoadConstantInstructionBytecodeEvent.getConstant());
    }

    @Override
    public void visitLookupSwitchInstruction(
        final LookupSwitchInstructionBytecodeEvent aLookupSwitchInstructionBytecodeEvent)
    {
        mMethodVisitor.visitLookupSwitchInsn(aLookupSwitchInstructionBytecodeEvent.getDefaultLabel(),
            aLookupSwitchInstructionBytecodeEvent.getKeys(), aLookupSwitchInstructionBytecodeEvent.getBlockLabels());
    }

    @Override
    public void visitMethodInstruction(final MethodInstructionBytecodeEvent aMethodInstructionBytecodeEvent) {
        mMethodVisitor.visitMethodInsn(aMethodInstructionBytecodeEvent.getOpcode(),
            aMethodInstructionBytecodeEvent.getOwnerName(), aMethodInstructionBytecodeEvent.getMethodName(),
            aMethodInstructionBytecodeEvent.getMethodDescriptor());
    }

    @Override
    public void visitMultiANewArrayInstruction(
        final MultiANewArrayInstructionBytecodeEvent aMultiANewArrayInstructionBytecodeEvent)
    {
        mMethodVisitor.visitMultiANewArrayInsn(aMultiANewArrayInstructionBytecodeEvent.getComponentDescriptor(),
            aMultiANewArrayInstructionBytecodeEvent.getDimensions());
    }

    @Override
    public void visitTableSwitchInstruction(
        final TableSwitchInstructionBytecodeEvent aTableSwitchInstructionBytecodeEvent)
    {
        mMethodVisitor.visitTableSwitchInsn(aTableSwitchInstructionBytecodeEvent.getMinimumKeyValue(),
            aTableSwitchInstructionBytecodeEvent.getMaximumKeyValue(),
            aTableSwitchInstructionBytecodeEvent.getDefaultLabel(),
            aTableSwitchInstructionBytecodeEvent.getBlockLabels());
    }

    @Override
    public void visitTypeInstruction(final TypeInstructionBytecodeEvent aTypeInstructionBytecodeEvent) {
        mMethodVisitor.visitTypeInsn(aTypeInstructionBytecodeEvent.getOpcode(),
            aTypeInstructionBytecodeEvent.getTypeName());
    }

    @Override
    public void visitVariableInstruction(final VariableInstructionBytecodeEvent aVariableInstructionBytecodeEvent) {
        mMethodVisitor.visitVarInsn(aVariableInstructionBytecodeEvent.getOpcode(),
            aVariableInstructionBytecodeEvent.getLocalVariableIndex());
    }

    @Override
    public void visitLabel(final LabelBytecodeEvent aLabelBytecodeEvent) {
        mMethodVisitor.visitLabel(aLabelBytecodeEvent.getLabel());
    }

    @Override
    public void visitTryCatchBlock(final TryCatchBlockBytecodeEvent aTryCatchBlockBytecodeEvent) {
        mMethodVisitor.visitTryCatchBlock(aTryCatchBlockBytecodeEvent.getStartLabel(),
            aTryCatchBlockBytecodeEvent.getEndLabel(), aTryCatchBlockBytecodeEvent.getHandlerLabel(),
            aTryCatchBlockBytecodeEvent.getExceptionName());
    }

    @Override
    public void visitLocalVariable(final LocalVariableBytecodeEvent aLocalVariableBytecodeEvent) {
        mMethodVisitor.visitLocalVariable(aLocalVariableBytecodeEvent.getName(),
            aLocalVariableBytecodeEvent.getDescriptor(), aLocalVariableBytecodeEvent.getSignature(),
            aLocalVariableBytecodeEvent.getStartScopeLabel(), aLocalVariableBytecodeEvent.getEndScopeLabel(),
            aLocalVariableBytecodeEvent.getLocalVariableIndex());
    }

    @Override
    public void visitLineNumber(final LineNumberBytecodeEvent aLineNumberBytecodeEvent) {
        mMethodVisitor.visitLineNumber(aLineNumberBytecodeEvent.getLineNumber(),
            aLineNumberBytecodeEvent.getStartLabel());
    }

    @Override
    public void visitCodeEnd(final CodeEndBytecodeEvent aCodeEndBytecodeEvent) {
        mMethodVisitor.visitMaxs(aCodeEndBytecodeEvent.getMaximumStackSize(),
            aCodeEndBytecodeEvent.getMaximumLocalVariables());
    }

    @Override
    public void visitMethodEnd(final MethodEndBytecodeEvent aMethodEndBytecodeEvent) {
        mMethodVisitor.visitEnd();
    }
}
